// Go作者版权所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package ecdsa

import (
	"crypto/cipher"
	"crypto/elliptic"
	"internal/cpu"
	"math/big"
)

// kdsa调用“计算数字签名身份验证”
// 具有给定功能代码和4096字节的指令
// 参数块。
// None
// 返回值对应于由设置的条件代码
// 指示中断的调用由
// 作用
// 围棋：noescape
func kdsa(fc uint64, params *[4096]byte) (errn uint64)

// testingDisableKDSA强制使用通用回退路径。它只能在测试中设置。
var testingDisableKDSA bool

// canUseKDSA检查KDSA指令是否可用，如果可用，则进行检查
// 曲线的名称，以查看其是否匹配支持的曲线（P-256、P-384、P-521）。
// 然后，基于曲线名称，将指定功能代码和块大小。
// 如果KDSA指令不可用或曲线不受支持，则可以使用KDSA
// 将“确定”设置为false。
func canUseKDSA(c elliptic.Curve) (functionCode uint64, blockSize int, ok bool) {
	if testingDisableKDSA {
		return 0, 0, false
	}
	if !cpu.S390X.HasECDSA {
		return 0, 0, false
	}
	switch c.Params().Name {
	case "P-256":
		return 1, 32, true
	case "P-384":
		return 2, 48, true
	case "P-521":
		return 3, 80, true
	}
	return 0, 0, false // 不匹配
}

func hashToBytes(dst, hash []byte, c elliptic.Curve) {
	l := len(dst)
	if n := c.Params().N.BitLen(); n == l*8 {
		// 长度为整数字节的曲线的分配自由路径
		if len(hash) >= l {
			// 截断散列
			copy(dst, hash[:l])
			return
		}
		// 带前导零的pad散列
		p := l - len(hash)
		for i := 0; i < p; i++ {
			dst[i] = 0
		}
		copy(dst[p:], hash)
		return
	}
	// TODO（mundaym）：在这里避免hashToInt呼叫
	hashToInt(hash, c).FillBytes(dst)
}

func sign(priv *PrivateKey, csprng *cipher.StreamReader, c elliptic.Curve, hash []byte) (r, s *big.Int, err error) {
	if functionCode, blockSize, ok := canUseKDSA(c); ok {
		for {
			var k *big.Int
			k, err = randFieldElement(c, *csprng)
			if err != nil {
				return nil, nil, err
			}

			// 符号的参数块如下所示。
			// +---------------------+
			// |签名（R）|
			// +---------------------+
			// |签名|
			// +---------------------+
			// |散列消息|
			// +---------------------+
			// |私钥|
			// +---------------------+
			// |随机数|
			// +---------------------+
			// |                     |
			// |        ...          |
			// |                     |
			// +---------------------+
			// 常见组件（签名器、签名、哈希消息、私钥和
			// 随机数）每个数据块的大小为字节。块大小对于不同的对象不同
			// 不同的曲线和由canUseKDSA函数设置。
			var params [4096]byte

			// 将内容复制到参数块中。在标志箱中，
			// 我们将散列消息、私钥和随机数复制到
			// 参数块。
			hashToBytes(params[2*blockSize:3*blockSize], hash, c)
			priv.D.FillBytes(params[3*blockSize : 4*blockSize])
			k.FillBytes(params[4*blockSize : 5*blockSize])
			// 通过添加8将验证功能代码转换为符号功能代码。
			// 我们还需要在函数代码中设置“确定性”位
			// 添加128，以使用自身的随机数停止指令
			// 除了我们提供的随机数之外，还有一个生成器。
			switch kdsa(functionCode+136, &params) {
			case 0: // 成功
				r = new(big.Int)
				r.SetBytes(params[:blockSize])
				s = new(big.Int)
				s.SetBytes(params[blockSize : 2*blockSize])
				return
			case 1: // 错误
				return nil, nil, errZeroParam
			case 2: // 重试
				continue
			}
			panic("unreachable")
		}
	}
	return signGeneric(priv, csprng, c, hash)
}

func verify(pub *PublicKey, c elliptic.Curve, hash []byte, r, s *big.Int) bool {
	if functionCode, blockSize, ok := canUseKDSA(c); ok {
		// 验证的参数块如下所示：
		// +---------------------+
		// |签名（R）|
		// +---------------------+
		// |签名|
		// +---------------------+
		// |散列消息|
		// +---------------------+
		// |公钥X|
		// +---------------------+
		// |公钥Y|
		// +---------------------+
		// |                     |
		// |        ...          |
		// |                     |
		// +---------------------+
		// 公共组件（签名器、签名、哈希消息、公钥X、，
		// 公钥Y）的每个块大小为字节。块大小对于不同的对象不同
		// 不同的曲线和由canUseKDSA函数设置。
		var params [4096]byte

		// 将内容复制到参数块中。在核实案件中，
		// 我们复制签名（r）、签名（s）、散列消息、公钥x组件、，
		// 并将公钥y分量放入参数块中。
		r.FillBytes(params[0*blockSize : 1*blockSize])
		s.FillBytes(params[1*blockSize : 2*blockSize])
		hashToBytes(params[2*blockSize:3*blockSize], hash, c)
		pub.X.FillBytes(params[3*blockSize : 4*blockSize])
		pub.Y.FillBytes(params[4*blockSize : 5*blockSize])
		return kdsa(functionCode, &params) == 0
	}
	return verifyGeneric(pub, c, hash, r, s)
}
